Skip to content

Client #93: Coach role pages#102

Merged
raphael-frank merged 13 commits into
mainfrom
client/93-coach-role-pages
Jul 6, 2026
Merged

Client #93: Coach role pages#102
raphael-frank merged 13 commits into
mainfrom
client/93-coach-role-pages

Conversation

@FadyGergesRezk

@FadyGergesRezk FadyGergesRezk commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Why

Build the Coach (trainer) role experience so a coach can see their team on the dashboard, run their team's events end to end, and give/track feedback for their trainees, without any of it leaking to other roles.

What changed

  • Dashboard: coach's team name and roster size now render, sourced from the dashboard envelope
  • Events: coach/director/admin can create events; creator-gated edit/delete; attendance upkeep (remove no-shows); personal attended/missed status no longer shown to non-trainee roles
  • Teams: "My Teams" now includes teams the current user coaches, not just teams they train on
  • Members: team/sport filter dropdowns are scoped to what the viewer can actually see, instead of listing the whole club
  • Feedback: per-person compose dialog, coach coverage tracking (which trainees still need feedback), page framing/filters adapted for a coach giving feedback rather than a trainee receiving it
  • Added shadcn dialog/alert-dialog/label/textarea primitives and a shared server-error message helper used across the new forms
  • Development: confirm before deleting a generated report (adjacent UX fix reusing the new dialog primitives)

Notes

  • Team management (create/edit/delete a team) and Letters compose for the coach role are not part of this PR — they're tracked as separate follow-up work; Feature: Coach (Trainer) role — pages #93 is left open until those land.
  • Mutation mock branches (mockOr) validate the same server-enforced rules (trainer-of-subject-trainee, creator-or-admin) and now mutate in-memory fixture state, matching the pattern already used by sport-events, so the demo persona reflects create/delete immediately.

Testing

  • pnpm -C web-client lint, typecheck, build, and test all pass (74 tests)
  • Added unit tests for the feedback coverage logic, the "my teams" trainer fix, and the member filter scoping fix

Relates to #93

Summary by CodeRabbit

  • New Features

    • Added a full dashboard with summary cards and dedicated sections for events, feedback, and organization data.
    • Introduced richer management screens for members, teams, sport events, reports, feedback, and letters, including filters, details, compose, edit, and delete actions.
    • Added reusable dialog, table toolbar, label, textarea, and formatting components/utilities for a more consistent UI.
  • Bug Fixes

    • Improved loading, empty, and error handling across key pages.
    • Updated form and status handling so actions reflect the current user’s role and available data.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR rebuilds the web-client with a role-based type model, mock/live data-switching infrastructure, shared UI primitives, and rewritten feature pages (dashboard, members, feedback, organization, letters, sport-events, helper) driven by view-model hooks. Separately, spring-event gains trainer/director entities and authorization for event creation.

Changes

Web client mock-driven feature rebuild

Layer / File(s) Summary
Types, role model, shared utilities
web-client/src/types.ts, web-client/src/lib/format.ts, web-client/src/lib/server-error.ts, web-client/src/lib/id-selection.ts
Adds Reference/Role/Dashboard/Report types, highestRole/roleLabel helpers, formatting utilities, server-error extraction, and ID-list helpers.
Dashboard fixtures
web-client/src/mocks/fixtures/dashboard.ts
Precomputes per-persona Dashboard fixtures and a dashboardForUser resolver.
Shared UI primitives
web-client/src/components/ui/dialog.tsx, alert-dialog.tsx, label.tsx, textarea.tsx, table-toolbar.tsx, input.tsx
Adds Dialog/AlertDialog/Label/Textarea/TableToolbar wrappers and an Input style tweak.
Dashboard feature
web-client/src/app/pages/DashboardPage.tsx, model/useDashboardViewModel.ts, __tests__/DashboardPage.test.tsx
Adds a role-based dashboard view model and page with stat cards/sections plus tests.
Members feature
web-client/src/features/members/...
Adds view model with role-scoped composable member IDs/filters and a rewritten members table page.
Feedback feature
web-client/src/features/feedback/...
Adds mock-aware queries, UI store, coverage-aware view model, compose dialog, and rewritten feedback page.
Organization feature
web-client/src/features/organization/...
Adds mock-aware team/sport queries, team editor model/dialog, and a rewritten organization page with roster sheets.
Letters feature
web-client/src/features/letters/...
Routes mail/PDF hooks through mock switching and rewrites the letters composer page.
Sport-events feature
web-client/src/features/sport-events/...
Adds a mock event store, UI store, view model with attendance status logic, editor dialog, and rewritten events page.
Helper reports feature
web-client/src/features/helper/...
Adds mock-aware report queries/mutations and a rewritten helper page with report list/detail/delete.

Event creation authorization

Layer / File(s) Summary
Trainer/Director shadow entities
entity/DirectorEntity.java, entity/TrainerEntity.java, entity/TeamEntity.java, repository/DirectorRepository.java, repository/TrainerRepository.java
Adds read-only entities/repositories for trainers and directors with composite keys.
createEvent permission enforcement
service/EventService.java, test/.../EventServiceTest.java
Adds canCreateEvent authorization based on trainer/director membership and corresponding tests.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SportEventEditorDialog
  participant useCreateSportEvent
  participant mockOr
  participant QueryCache
  SportEventEditorDialog->>useCreateSportEvent: mutateAsync(payload)
  useCreateSportEvent->>mockOr: select mock or live
  mockOr-->>QueryCache: setQueryData(eventKeys.detail)
  useCreateSportEvent-->>SportEventEditorDialog: close editor, notice
Loading
sequenceDiagram
  participant Client
  participant EventService
  participant TrainerRepository
  participant DirectorRepository
  Client->>EventService: createEvent(requesterId, body)
  EventService->>EventService: check isAdmin
  EventService->>TrainerRepository: existsById(teamId, requesterId)
  TrainerRepository-->>EventService: false
  EventService->>DirectorRepository: existsById(sportId, requesterId)
  DirectorRepository-->>EventService: true/false
  EventService-->>Client: created or ForbiddenException
Loading

Suggested labels: enhancement

Suggested reviewers: raphael-frank

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is clearly related to the PR’s main theme of adding coach-role client support and page updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch client/93-coach-role-pages

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 PMD (7.25.0)
services/spring-event/src/main/java/tum/devoops/eventservice/repository/DirectorRepository.java

No java executable found in PATH


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (13)
web-client/src/app/pages/model/useDashboardViewModel.ts (1)

203-217: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Fetch waterfall: org/events queries gated on data?.role instead of the already-known user.role.

sportsQuery/teamsQuery/eventsQuery are only enabled once dashboardQuery resolves and exposes data.role, even though role = user.role (line 219) is available synchronously from useAuth(). This forces a sequential round trip (dashboard fetch → then org/events fetch) instead of firing them in parallel, adding latency to admin and trainee/trainer/director dashboards alike.

If the token role reliably predicts the envelope role (which shouldShowBalance below already assumes), gating on role instead of data?.role would let these fire in parallel with the dashboard call.

♻️ Proposed fix
-  const isAdmin = data?.role === 'admin'
+  const isAdmin = role === 'admin'
   const sportsQuery = useSportsList(isAdmin)
   const teamsQuery = useTeamsList(isAdmin)

   // ...
-  const isNonAdmin = !!data && data.role !== 'admin'
+  const isNonAdmin = role !== 'admin'
   const eventsQuery = useEventsList(isNonAdmin)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-client/src/app/pages/model/useDashboardViewModel.ts` around lines 203 -
217, The org/events queries in useDashboardViewModel are being gated on
dashboardQuery.data.role, which creates a fetch waterfall even though useAuth
already provides user.role synchronously. Update the enablement logic for
sportsQuery, teamsQuery, and eventsQuery to key off the already-known role (or
an equivalent derived value) instead of data?.role, while keeping the existing
admin/non-admin branching behavior intact. This will let the dashboard and
dependent queries start in parallel without changing the role-based routing in
useDashboardViewModel.
web-client/src/features/feedback/model/useFeedbackViewModel.ts (1)

290-296: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Gate useTeamsList here; useMembers needs an enabled option first
buildFeedbackCoverage() returns null for non-trainers, so useTeamsList can be skipped on those roles. useMembers is still always on, so add an enabled param there if you want to avoid that fetch too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-client/src/features/feedback/model/useFeedbackViewModel.ts` around lines
290 - 296, Gate the team/member fetches in useFeedbackViewModel so non-trainers
do not load unnecessary data: use the user.role check to conditionally run
useTeamsList since buildFeedbackCoverage() returns null for non-trainers, and
update useMembers to accept an enabled option so its fetch can also be skipped
when the role should not load members. Keep the change localized to
useFeedbackViewModel and the useMembers hook contract so the enabled flag
controls the query behavior.
web-client/src/mocks/fixtures/organization.ts (1)

756-758: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hardcoded TEAM_U16 id with a name that doesn't match the referenced team.

The comment says "first team the signed-in member belongs to," but the constant is named TEAM_U16 while it points to "Football Juniors" (bbbbbbbb-0001…), which isn't described as a U16-specific squad anywhere in this file. Consider deriving this from myTeamFixtures[0]?.id (or renaming to something accurate) so it can't drift from CURRENT_MEMBER_ID/myTeamFixtures if either changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-client/src/mocks/fixtures/organization.ts` around lines 756 - 758, The
TEAM_U16 constant is hardcoded and may drift from the actual first team for the
signed-in member. Update the fixture in organization.ts so the exported team id
is derived from myTeamFixtures[0]?.id (or rename the constant to match the
actual team it represents) and keep it aligned with CURRENT_MEMBER_ID and the
first entry in myTeamFixtures.
web-client/src/mocks/scope.test.ts (1)

1-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

scopeTeamReport has no test coverage.

scope.ts exports scopeTeamReport (used to gate team-report visibility for trainer/director/member roles), but this file only imports/tests scopeBalances, scopeEvents, scopeFeedback, scopeMembers, scopeReport, scopeTransactions. Given this function guards role-based access to team reports, add a describe('scopeTeamReport', ...) block mirroring the scopeReport tests (trainer sees own-team reports, director sees own-sport's teams, member is always denied).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-client/src/mocks/scope.test.ts` around lines 1 - 22, Add test coverage
for scopeTeamReport in the scope.test suite. The current test file imports
scopeBalances, scopeEvents, scopeFeedback, scopeMembers, scopeReport, and
scopeTransactions but never exercises scopeTeamReport, so create a
describe('scopeTeamReport', ...) block alongside the existing role-based scope
tests. Mirror the scopeReport patterns using MOCK_PERSONAS and the fixtures
helpers to verify trainer access to own-team reports, director access to
own-sport teams, and that member access is always denied.
web-client/vite.config.ts (1)

21-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the rewrite regex into a testable pure function.

The path-rewrite logic (query splitting, service-segment duplication, trailing-slash trim) is non-trivial and currently only lives inline in the Vite config with no unit coverage. A regex edit (new service name, edge case) could silently break every dev API call. Since dev-proxy misbehavior is easy to miss until manual testing, a small extracted/tested helper would catch regressions early.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-client/vite.config.ts` around lines 21 - 34, The inline path rewrite
logic in the Vite config is too complex to leave untested, so extract it from
the rewrite callback into a pure helper that handles query splitting, service
duplication, and trailing-slash normalization. Move the regex-based
transformation from the Vite proxy’s rewrite function into a named function that
can be imported and unit tested, then keep the config callback as a thin wrapper
around that helper. Add tests for the extracted helper covering existing service
names, query strings, and trailing slash cases so future regex changes don’t
silently break API calls.
web-client/src/features/auth/currentUser.ts (1)

19-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No test coverage for the mock-persona identity path.

getCurrentUser() branches on USE_MOCKS/VITE_MOCK_PERSONA, but only the tokenUser() fallback path is exercised in useAuth.test.ts. Given this function is the single source of identity for role-gated navigation and data scoping across the app, consider adding coverage for: USE_MOCKS=true with a valid persona key, an invalid/unset VITE_MOCK_PERSONA (should default to 'member'), and USE_MOCKS=false (falls through to token).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-client/src/features/auth/currentUser.ts` around lines 19 - 29, Add test
coverage for the identity branching in getCurrentUser/mockPersona so the
mock-persona path is exercised, not just tokenUser(). In useAuth.test.ts, cover
USE_MOCKS=true with a valid VITE_MOCK_PERSONA key, an invalid or unset
VITE_MOCK_PERSONA defaulting to the 'member' entry in MOCK_PERSONAS, and
USE_MOCKS=false falling through to tokenUser(); use the getCurrentUser and
mockPersona symbols to locate the logic.
web-client/src/__tests__/useAuth.test.ts (1)

1-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tests implicitly depend on USE_MOCKS being false; not isolated from env.

Both tests set keycloakMock.tokenParsed and expect useAuth() to use the token path, but getCurrentUser() now checks mockPersona() first: if (!USE_MOCKS) return null (currentUser.ts, Line 20). Since USE_MOCKS is derived from import.meta.env.VITE_USE_MOCKS at module load and neither mockSwitch nor personas is mocked here, these assertions silently rely on that env var being unset/false during the vitest run. If a root .env sets VITE_USE_MOCKS=true (loaded by Vite for any mode unless overridden by .env.test), getCurrentUser() would return a mock persona instead of the token-derived user, and both tests would fail for a reason unrelated to the code under test.

Consider explicitly mocking @/mocks/mockSwitch to force the token path, making the test deterministic regardless of environment configuration.

🧪 Suggested test isolation fix
 import { describe, expect, it, vi } from 'vitest'

+vi.mock('`@/mocks/mockSwitch`', () => ({ USE_MOCKS: false, mockOr: (_m: unknown, live: () => unknown) => live() }))
+
 const keycloakMock = {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-client/src/__tests__/useAuth.test.ts` around lines 1 - 34, The useAuth
tests are implicitly relying on the runtime value of USE_MOCKS, which makes them
environment-dependent and brittle. Update the test setup in useAuth.test.ts to
explicitly mock the mock-switch path used by currentUser/getCurrentUser so the
tokenParsed branch is always exercised, regardless of VITE_USE_MOCKS. Keep the
assertions focused on useAuth, keycloakMock, and the getCurrentUser/mockPersona
flow so the tests remain deterministic.
web-client/src/features/organization/api/queries.ts (1)

47-61: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider retry: false for single-item lookups.

When the mock/live lookup misses, Sport not found / Team not found is a deterministic error, not a transient one. Without retry: false, react-query's default retry behavior will retry the failed query multiple times before surfacing the error, delaying any "not found" UI state.

Also applies to: 113-127

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-client/src/features/organization/api/queries.ts` around lines 47 - 61,
The single-item lookup queries in useSport and the matching useTeam hook are
throwing deterministic “not found” errors, so react-query should not retry them.
Add retry: false to the useQuery options for these id-based lookups so a missing
Sport/Team fails immediately and the UI can show the not-found state without
delay.
web-client/src/features/organization/model/useTeamsViewModel.ts (1)

16-21: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

SportTeamsView drops id, forcing downstream code to key off name.

OrganizationPage.tsx uses sport.name for the React key, the activeOpenSport expand/collapse state, and stats.mySports (a Set of sportName). If two sports ever share the same name, this causes duplicate React keys, colliding expand/collapse state between unrelated sports, and undercounted mySports. Prefer carrying the sport id through SportTeamsView and using it as the stable identifier.

♻️ Proposed fix
 export interface SportTeamsView {
+  id: string
   name: string
   description: string
   directors: MemberRef[]
   teams: TeamView[]
 }
   const joinedSports = sports.map((sport) => ({
+    id: sport.id,
     name: sport.name,
     description: sport.description,
     directors: sport.directors,
     teams: teamsBySportId.get(sport.id) ?? [],
   }))

Also applies to: 59-64

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-client/src/features/organization/model/useTeamsViewModel.ts` around lines
16 - 21, SportTeamsView is missing the sport identifier, which forces downstream
consumers to rely on name as the stable key. Update the SportTeamsView interface
and the related mapping in useTeamsViewModel so the sport id is preserved
alongside name, then switch OrganizationPage.tsx and any consumers of
activeOpenSport and stats.mySports to use that id as the unique identifier
instead of sport.name.
web-client/src/features/helper/model/useReportViewModel.ts (1)

46-52: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Trainer with no matched team surfaces a generic "Report not allowed" error.

If team is null (trainer not yet linked to any team, or teamsQuery hasn't resolved), generateTeam is created with teamId=''; clicking generate will fail inside scopeTeamReport('', ...) and surface generateError.message as the unhelpful "Report not allowed" string in the UI, rather than indicating the real cause (no team assigned).

Consider disabling the generate action (or short-circuiting generate) when scope === 'team' && !team.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-client/src/features/helper/model/useReportViewModel.ts` around lines 46 -
52, The team-report generation path in useReportViewModel is creating a
generator with an empty team id when team is null, which leads to the generic
failure message instead of a clear no-team state. Update the
generateTeam/useGenerateTeamReport flow so that when scope is 'team' and team is
missing, the generate action is disabled or short-circuited before calling
scopeTeamReport, and only use generateTeam when team?.id is available.
web-client/src/features/helper/api/queries.ts (1)

96-128: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Per-report read/delete skip the scope checks used elsewhere in this file.

useReport (lines 98-111) and useDeleteReport (lines 116-128) look up/delete by reportId alone with no scopeReport/scopeTeamReport check in the mock branch, unlike useGenerateMemberReport/useGenerateTeamReport and the list hooks, which all gate access via scope. Today this is masked because reportId only ever comes from an already-scoped list row, but it's an inconsistent invariant: any code path (or future URL-driven detail view) that supplies an arbitrary reportId could read or delete a report the current mock user shouldn't have access to.

🔒 Suggested fix: reuse scope helpers for single-report access
 export function useReport(reportId: string | null) {
   return useQuery<Report>({
     queryKey: helperKeys.report(reportId ?? ''),
     enabled: !!reportId,
     queryFn: () =>
       mockOr(
         () => {
           const found = reportId ? reportById[reportId] : undefined
-          if (!found) throw new Error('Report not found')
+          if (!found) throw new Error('Report not found')
+          const allowed =
+            found.kind === 'team'
+              ? scopeTeamReport(found.team?.id ?? '', getCurrentUser())
+              : scopeReport(found.member?.id ?? '', getCurrentUser())
+          if (!allowed) throw new Error('Report not found')
           return Promise.resolve(found)
         },
         () => helperClient.get<Report>(`/reports/${reportId}`).then(r => r.data),
       ),
   })
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-client/src/features/helper/api/queries.ts` around lines 96 - 128, The
single-report hooks bypass the same scope guard used elsewhere, so align
`useReport` and `useDeleteReport` with the existing scoped access pattern. In
the mock branch of `queryFn` and `mutationFn`, resolve the report through
`scopeReport` or `scopeTeamReport` before reading or deleting, instead of using
`reportId` directly. Reuse the same helper logic already present in
`useGenerateMemberReport`, `useGenerateTeamReport`, and the list hooks so
`helperKeys.report`, `reportById`, and `helperClient` only operate on scoped
reports.
web-client/src/features/sport-events/model/useEventsViewModel.ts (1)

25-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

missedCount is declared but never populated.

EventsView.missedCount is an optional field that buildEventsView never sets, and SportEventsPage.tsx's stat cards only read stats.upcoming/thisWeek/total. This looks like leftover scaffolding for an unfinished "missed" stat card — either wire it up or drop the field to avoid misleading future consumers who might rely on it being present.

Also applies to: 156-164

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-client/src/features/sport-events/model/useEventsViewModel.ts` around
lines 25 - 34, The EventsView.missedCount field is leftover scaffolding because
buildEventsView does not populate it and SportEventsPage only consumes
stats.upcoming/thisWeek/total. Either remove missedCount from EventsView and any
related typing in useEventsViewModel/buildEventsView, or wire it through
consistently by computing and assigning it where the view model is built and
updating the consuming UI if it is meant to be displayed.
web-client/src/features/sport-events/api/queries.ts (1)

86-88: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract canManageEvent for reuse instead of duplicating the check in the UI.

SportEventsPage.tsx (line 274) reimplements this exact condition (user.role === 'admin' || detail.creator?.id === user.id) inline. Exporting this helper and importing it in the page would prevent the two authorization checks from silently drifting apart.

♻️ Proposed refactor
-function canManageEvent(user: AuthUser, event: SportEvent): boolean {
+export function canManageEvent(user: AuthUser, event: SportEvent): boolean {
   return user.role === 'admin' || event.creator?.id === user.id
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-client/src/features/sport-events/api/queries.ts` around lines 86 - 88,
Export the canManageEvent helper from queries.ts and reuse it in SportEventsPage
instead of keeping the same authorization condition inline. Update the existing
canManageEvent function to be the single source of truth for the
admin-or-creator check, then import and call it from the page where the inline
user.role === 'admin' || detail.creator?.id === user.id logic currently lives.
This keeps the permission rule centralized and prevents the two checks from
diverging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web-client/src/app/pages/model/useDashboardViewModel.ts`:
- Around line 92-101: The trainer/coach dashboard is missing a loading state for
the team card because DashboardViewModel.states has no myTeam entry and
fillSections does not assign a skeleton for the trainer path. Update
useDashboardViewModel’s DashboardViewModel and fillSections logic to include a
myTeam DashboardSectionState during the loading branch, then thread that state
through DashboardPage.tsx so TeamCard can render a placeholder while data is
loading instead of appearing only after view.myTeam is populated.

In `@web-client/src/components/ui/alert-dialog.tsx`:
- Around line 27-41: The AlertDialogOverlay className uses an invalid Tailwind
v4 supports variant, so the backdrop blur utility is not generated. Update the
variant in AlertDialogOverlay to the bracketed supports-[...] form while keeping
the existing backdrop blur behavior, and make sure the cn() class string still
includes the blur utility for the overlay.

In `@web-client/src/components/ui/table-toolbar.tsx`:
- Around line 25-32: The local search state in table-toolbar.tsx is only
initialized from searchValue once, so draftSearch can get stale when the parent
updates the filter externally. Update the table toolbar component to resync
draftSearch whenever searchValue changes, using the existing useState/useEffect
flow around draftSearch, setDraftSearch, and the search input handlers. Make
sure the sync keeps user typing behavior intact while allowing external resets
like clear filters or tab changes to immediately reflect in the input.

In `@web-client/src/features/helper/api/queries.ts`:
- Around line 28-60: The mock generate mutations in useGenerateMemberReport and
useGenerateTeamReport only resolve and invalidate queries, so they never change
the static report fixtures. Update the mock branches to mutate the in-memory
records in web-client/src/mocks/fixtures/report.ts (including reportById,
memberReportSummariesById, and teamReportSummariesById) before invalidating
queries, so generated reports persist in mock mode and refetches return the
updated data.

In `@web-client/src/features/organization/pages/OrganizationPage.tsx`:
- Around line 319-323: The Coaches list in OrganizationPage is missing the
current user context, so MemberBadges cannot mark the signed-in coach as “you.”
Update the MemberBadges usage for team.trainers to pass the same currentUserId
value that RosterMembers already receives, ensuring MemberBadges can correctly
compute isCurrentUser and display the badge for the coach’s own name.

In `@web-client/src/features/sport-events/api/queries.ts`:
- Around line 106-127: The mock event creation path in mockCreateEvent is
missing the same role-based authorization used by mockUpdateEvent and
mockDeleteEvent. Add an authorization check before building/upserting the event,
reusing the existing canManageEvent or equivalent create-role gate so only
coach/director/admin can create events, and reject unauthorized callers of
useCreateSportEvent() in the mock API layer rather than relying only on
SportEventsPage.tsx.

In `@web-client/src/features/sport-events/components/SportEventEditorDialog.tsx`:
- Around line 186-203: The handleTeamToggle logic in SportEventEditorDialog is
preserving sports that were only added via previously selected teams because it
unions nextTeamIds’ sports into current.sportIds without removing deselected
team-driven sports. Update the team toggle flow so sportIds is derived from the
current teamIds selection (or split explicit sport picks from team-derived ones)
instead of only appending to current.sportIds, and keep syncAttendeesForTeams
aligned with the updated teamIds/sportIds state.

In `@web-client/src/lib/format.ts`:
- Around line 39-57: The date helpers in format.ts currently call new Date(iso)
and pass the result straight into Intl.DateTimeFormat.format, which can throw on
invalid timestamps. Update formatDate, formatDateShort, formatDateTime, and
formatTime to validate the parsed Date first and return a safe fallback when the
input is missing or malformed; use the existing helper names and DATE,
DATE_SHORT, WEEKDAY, and TIME formatters to keep the fix localized.

In `@web-client/src/mocks/fixtures/dashboard.ts`:
- Around line 69-99: The trainer/director dashboard mock is inconsistent because
`dashboard.ts` returns a single displayed `team` or `sport` from `trainerTeams`,
`directorSportIds`, and `sportRef`, but the aggregate fields still sum across
all matched teams/sports. Update the `trainer` and `director` cases so the
totals in `total_members`, `upcoming_events`, `recent_feedback`,
`sport_balance_cents`, and `teams` are scoped to the same selected team/sport,
or otherwise change the payload shape to represent multiple teams/sports
consistently. Use the existing symbols `trainerTeams`, `directorSportIds`,
`upcomingEvents`, `scopeFeedback`, and `teamBalance` to keep the data model
aligned.

---

Nitpick comments:
In `@web-client/src/__tests__/useAuth.test.ts`:
- Around line 1-34: The useAuth tests are implicitly relying on the runtime
value of USE_MOCKS, which makes them environment-dependent and brittle. Update
the test setup in useAuth.test.ts to explicitly mock the mock-switch path used
by currentUser/getCurrentUser so the tokenParsed branch is always exercised,
regardless of VITE_USE_MOCKS. Keep the assertions focused on useAuth,
keycloakMock, and the getCurrentUser/mockPersona flow so the tests remain
deterministic.

In `@web-client/src/app/pages/model/useDashboardViewModel.ts`:
- Around line 203-217: The org/events queries in useDashboardViewModel are being
gated on dashboardQuery.data.role, which creates a fetch waterfall even though
useAuth already provides user.role synchronously. Update the enablement logic
for sportsQuery, teamsQuery, and eventsQuery to key off the already-known role
(or an equivalent derived value) instead of data?.role, while keeping the
existing admin/non-admin branching behavior intact. This will let the dashboard
and dependent queries start in parallel without changing the role-based routing
in useDashboardViewModel.

In `@web-client/src/features/auth/currentUser.ts`:
- Around line 19-29: Add test coverage for the identity branching in
getCurrentUser/mockPersona so the mock-persona path is exercised, not just
tokenUser(). In useAuth.test.ts, cover USE_MOCKS=true with a valid
VITE_MOCK_PERSONA key, an invalid or unset VITE_MOCK_PERSONA defaulting to the
'member' entry in MOCK_PERSONAS, and USE_MOCKS=false falling through to
tokenUser(); use the getCurrentUser and mockPersona symbols to locate the logic.

In `@web-client/src/features/feedback/model/useFeedbackViewModel.ts`:
- Around line 290-296: Gate the team/member fetches in useFeedbackViewModel so
non-trainers do not load unnecessary data: use the user.role check to
conditionally run useTeamsList since buildFeedbackCoverage() returns null for
non-trainers, and update useMembers to accept an enabled option so its fetch can
also be skipped when the role should not load members. Keep the change localized
to useFeedbackViewModel and the useMembers hook contract so the enabled flag
controls the query behavior.

In `@web-client/src/features/helper/api/queries.ts`:
- Around line 96-128: The single-report hooks bypass the same scope guard used
elsewhere, so align `useReport` and `useDeleteReport` with the existing scoped
access pattern. In the mock branch of `queryFn` and `mutationFn`, resolve the
report through `scopeReport` or `scopeTeamReport` before reading or deleting,
instead of using `reportId` directly. Reuse the same helper logic already
present in `useGenerateMemberReport`, `useGenerateTeamReport`, and the list
hooks so `helperKeys.report`, `reportById`, and `helperClient` only operate on
scoped reports.

In `@web-client/src/features/helper/model/useReportViewModel.ts`:
- Around line 46-52: The team-report generation path in useReportViewModel is
creating a generator with an empty team id when team is null, which leads to the
generic failure message instead of a clear no-team state. Update the
generateTeam/useGenerateTeamReport flow so that when scope is 'team' and team is
missing, the generate action is disabled or short-circuited before calling
scopeTeamReport, and only use generateTeam when team?.id is available.

In `@web-client/src/features/organization/api/queries.ts`:
- Around line 47-61: The single-item lookup queries in useSport and the matching
useTeam hook are throwing deterministic “not found” errors, so react-query
should not retry them. Add retry: false to the useQuery options for these
id-based lookups so a missing Sport/Team fails immediately and the UI can show
the not-found state without delay.

In `@web-client/src/features/organization/model/useTeamsViewModel.ts`:
- Around line 16-21: SportTeamsView is missing the sport identifier, which
forces downstream consumers to rely on name as the stable key. Update the
SportTeamsView interface and the related mapping in useTeamsViewModel so the
sport id is preserved alongside name, then switch OrganizationPage.tsx and any
consumers of activeOpenSport and stats.mySports to use that id as the unique
identifier instead of sport.name.

In `@web-client/src/features/sport-events/api/queries.ts`:
- Around line 86-88: Export the canManageEvent helper from queries.ts and reuse
it in SportEventsPage instead of keeping the same authorization condition
inline. Update the existing canManageEvent function to be the single source of
truth for the admin-or-creator check, then import and call it from the page
where the inline user.role === 'admin' || detail.creator?.id === user.id logic
currently lives. This keeps the permission rule centralized and prevents the two
checks from diverging.

In `@web-client/src/features/sport-events/model/useEventsViewModel.ts`:
- Around line 25-34: The EventsView.missedCount field is leftover scaffolding
because buildEventsView does not populate it and SportEventsPage only consumes
stats.upcoming/thisWeek/total. Either remove missedCount from EventsView and any
related typing in useEventsViewModel/buildEventsView, or wire it through
consistently by computing and assigning it where the view model is built and
updating the consuming UI if it is meant to be displayed.

In `@web-client/src/mocks/fixtures/organization.ts`:
- Around line 756-758: The TEAM_U16 constant is hardcoded and may drift from the
actual first team for the signed-in member. Update the fixture in
organization.ts so the exported team id is derived from myTeamFixtures[0]?.id
(or rename the constant to match the actual team it represents) and keep it
aligned with CURRENT_MEMBER_ID and the first entry in myTeamFixtures.

In `@web-client/src/mocks/scope.test.ts`:
- Around line 1-22: Add test coverage for scopeTeamReport in the scope.test
suite. The current test file imports scopeBalances, scopeEvents, scopeFeedback,
scopeMembers, scopeReport, and scopeTransactions but never exercises
scopeTeamReport, so create a describe('scopeTeamReport', ...) block alongside
the existing role-based scope tests. Mirror the scopeReport patterns using
MOCK_PERSONAS and the fixtures helpers to verify trainer access to own-team
reports, director access to own-sport teams, and that member access is always
denied.

In `@web-client/vite.config.ts`:
- Around line 21-34: The inline path rewrite logic in the Vite config is too
complex to leave untested, so extract it from the rewrite callback into a pure
helper that handles query splitting, service duplication, and trailing-slash
normalization. Move the regex-based transformation from the Vite proxy’s rewrite
function into a named function that can be imported and unit tested, then keep
the config callback as a thin wrapper around that helper. Add tests for the
extracted helper covering existing service names, query strings, and trailing
slash cases so future regex changes don’t silently break API calls.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f495221-4725-4f89-ad58-15f703925f79

📥 Commits

Reviewing files that changed from the base of the PR and between 580dd23 and 6f2424b.

⛔ Files ignored due to path filters (4)
  • web-client/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • web-client/public/RoostFavIcon.svg is excluded by !**/*.svg
  • web-client/public/RoostIcon.svg is excluded by !**/*.svg
  • web-client/public/favicon.svg is excluded by !**/*.svg
📒 Files selected for processing (81)
  • .gitignore
  • web-client/.env.development.example
  • web-client/index.html
  • web-client/package.json
  • web-client/src/__tests__/DashboardPage.test.tsx
  • web-client/src/__tests__/useAuth.test.ts
  • web-client/src/app/layout/AppShell.tsx
  • web-client/src/app/pages/DashboardPage.tsx
  • web-client/src/app/pages/api/dashboardQueries.ts
  • web-client/src/app/pages/model/useDashboardViewModel.ts
  • web-client/src/app/router/routes.tsx
  • web-client/src/components/ui/alert-dialog.tsx
  • web-client/src/components/ui/badge.tsx
  • web-client/src/components/ui/calendar.tsx
  • web-client/src/components/ui/data-table.tsx
  • web-client/src/components/ui/date-range-filter.tsx
  • web-client/src/components/ui/dialog.tsx
  • web-client/src/components/ui/input.tsx
  • web-client/src/components/ui/label.tsx
  • web-client/src/components/ui/page-header.tsx
  • web-client/src/components/ui/popover.tsx
  • web-client/src/components/ui/select.tsx
  • web-client/src/components/ui/sidebar.tsx
  • web-client/src/components/ui/stat-card.tsx
  • web-client/src/components/ui/table-toolbar.tsx
  • web-client/src/components/ui/textarea.tsx
  • web-client/src/features/auth/currentUser.ts
  • web-client/src/features/auth/useAuth.ts
  • web-client/src/features/feedback/api/queries.ts
  • web-client/src/features/feedback/components/FeedbackComposeDialog.tsx
  • web-client/src/features/feedback/index.ts
  • web-client/src/features/feedback/model/feedbackUiStore.ts
  • web-client/src/features/feedback/model/useFeedbackViewModel.test.ts
  • web-client/src/features/feedback/model/useFeedbackViewModel.ts
  • web-client/src/features/feedback/pages/FeedbackPage.tsx
  • web-client/src/features/helper/api/queries.ts
  • web-client/src/features/helper/model/helperUiStore.ts
  • web-client/src/features/helper/model/useReportViewModel.ts
  • web-client/src/features/helper/pages/HelperPage.tsx
  • web-client/src/features/helper/pages/ReportMarkdown.test.tsx
  • web-client/src/features/helper/pages/ReportMarkdown.tsx
  • web-client/src/features/letters/api/queries.ts
  • web-client/src/features/letters/types/index.ts
  • web-client/src/features/members/api/queries.ts
  • web-client/src/features/members/model/membersUiStore.ts
  • web-client/src/features/members/model/useMembersViewModel.test.ts
  • web-client/src/features/members/model/useMembersViewModel.ts
  • web-client/src/features/members/pages/MembersPage.tsx
  • web-client/src/features/organization/api/queries.ts
  • web-client/src/features/organization/model/useTeamsViewModel.test.ts
  • web-client/src/features/organization/model/useTeamsViewModel.ts
  • web-client/src/features/organization/pages/OrganizationPage.tsx
  • web-client/src/features/payments/api/queries.ts
  • web-client/src/features/payments/model/paymentsUiStore.ts
  • web-client/src/features/payments/model/usePaymentsViewModel.ts
  • web-client/src/features/payments/pages/PaymentsPage.tsx
  • web-client/src/features/sport-events/api/queries.ts
  • web-client/src/features/sport-events/components/SportEventEditorDialog.tsx
  • web-client/src/features/sport-events/model/eventsUiStore.ts
  • web-client/src/features/sport-events/model/useEventsViewModel.test.ts
  • web-client/src/features/sport-events/model/useEventsViewModel.ts
  • web-client/src/features/sport-events/pages/SportEventsPage.tsx
  • web-client/src/index.css
  • web-client/src/lib/format.ts
  • web-client/src/lib/server-error.ts
  • web-client/src/mocks/fixtures/dashboard.ts
  • web-client/src/mocks/fixtures/events.ts
  • web-client/src/mocks/fixtures/feedback.ts
  • web-client/src/mocks/fixtures/finance.ts
  • web-client/src/mocks/fixtures/index.ts
  • web-client/src/mocks/fixtures/members.ts
  • web-client/src/mocks/fixtures/organization.ts
  • web-client/src/mocks/fixtures/report.ts
  • web-client/src/mocks/mockSwitch.ts
  • web-client/src/mocks/personas.ts
  • web-client/src/mocks/scope.test.ts
  • web-client/src/mocks/scope.ts
  • web-client/src/types.test.ts
  • web-client/src/types.ts
  • web-client/src/vite-env.d.ts
  • web-client/vite.config.ts

Comment thread web-client/src/app/pages/model/useDashboardViewModel.ts
Comment thread web-client/src/components/ui/alert-dialog.tsx
Comment on lines +25 to +32
const searchId = useId()
const [draftSearch, setDraftSearch] = useState(searchValue)

useEffect(() => {
const timeout = window.setTimeout(() => onSearchChange(draftSearch), 250)

return () => window.clearTimeout(timeout)
}, [draftSearch, onSearchChange])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

draftSearch never resyncs when searchValue changes externally.

draftSearch is only seeded from searchValue on mount. If a parent clears/resets the search filter externally (e.g., a "clear filters" button, tab switch, or programmatic reset), the input keeps showing the stale local value since there's no effect syncing draftSearch to prop changes.

🐛 Proposed fix to sync draftSearch with external searchValue changes
   const searchId = useId()
   const [draftSearch, setDraftSearch] = useState(searchValue)
 
+  useEffect(() => {
+    setDraftSearch(searchValue)
+  }, [searchValue])
+
   useEffect(() => {
     const timeout = window.setTimeout(() => onSearchChange(draftSearch), 250)
 
     return () => window.clearTimeout(timeout)
   }, [draftSearch, onSearchChange])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const searchId = useId()
const [draftSearch, setDraftSearch] = useState(searchValue)
useEffect(() => {
const timeout = window.setTimeout(() => onSearchChange(draftSearch), 250)
return () => window.clearTimeout(timeout)
}, [draftSearch, onSearchChange])
const searchId = useId()
const [draftSearch, setDraftSearch] = useState(searchValue)
useEffect(() => {
setDraftSearch(searchValue)
}, [searchValue])
useEffect(() => {
const timeout = window.setTimeout(() => onSearchChange(draftSearch), 250)
return () => window.clearTimeout(timeout)
}, [draftSearch, onSearchChange])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-client/src/components/ui/table-toolbar.tsx` around lines 25 - 32, The
local search state in table-toolbar.tsx is only initialized from searchValue
once, so draftSearch can get stale when the parent updates the filter
externally. Update the table toolbar component to resync draftSearch whenever
searchValue changes, using the existing useState/useEffect flow around
draftSearch, setDraftSearch, and the search input handlers. Make sure the sync
keeps user typing behavior intact while allowing external resets like clear
filters or tab changes to immediately reflect in the input.

Comment thread web-client/src/features/helper/api/queries.ts
Comment thread web-client/src/features/organization/pages/OrganizationPage.tsx
Comment thread web-client/src/features/sport-events/api/queries.ts
Comment thread web-client/src/lib/format.ts
Comment thread web-client/src/mocks/fixtures/dashboard.ts
@FadyGergesRezk FadyGergesRezk linked an issue Jul 3, 2026 that may be closed by this pull request
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

The agent ran but didn't make any changes. The issues may already be fixed or require manual intervention.

@FadyGergesRezk FadyGergesRezk self-assigned this Jul 3, 2026
raphael-frank and others added 2 commits July 6, 2026 14:28
POST /events was gated only by the blanket hasAnyRole('admin','member')
check, so any authenticated member (including plain trainees) could
create events, and a trainer/director could create one for a team/sport
they don't actually coach/direct -- contradicting the endpoint's own
OpenAPI description ("Directors: for their sport. Trainers: for their
team. Admins: any event.").

Adds read-only shadows of organization.trainers/organization.directors
(same pattern already used for TeamEntity/SportEntity here, and for all
four in spring-letter) plus a sport_id column on the existing TeamEntity
shadow, so createEvent can check the requester is a trainer of one of
the linked teams, or a director of one of the linked/team-derived
sports, before saving -- otherwise 403.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@raphael-frank

Copy link
Copy Markdown
Collaborator

lgtm!

@raphael-frank raphael-frank merged commit e06cf10 into main Jul 6, 2026
17 of 18 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@services/spring-event/src/main/java/tum/devoops/eventservice/service/EventService.java`:
- Around line 192-215: The canCreateEvent method in EventService currently
grants access when requesterId matches any single team or sport in the request,
which allows mixed authorized and unauthorized links to slip through. Update the
validation logic so every linked team in teamsLinked and every linked sport in
sportsLinked is checked individually against trainerRepository and
directorRepository, rather than using anyMatch over the whole set. Keep the
existing parsing and repository lookups, but change the authorization flow to
reject the request unless all linked entities are in scope for the requester.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 06938f0d-a088-4475-8788-15779f16bf62

📥 Commits

Reviewing files that changed from the base of the PR and between 8bc756b and 7f9a647.

📒 Files selected for processing (7)
  • services/spring-event/src/main/java/tum/devoops/eventservice/entity/DirectorEntity.java
  • services/spring-event/src/main/java/tum/devoops/eventservice/entity/TeamEntity.java
  • services/spring-event/src/main/java/tum/devoops/eventservice/entity/TrainerEntity.java
  • services/spring-event/src/main/java/tum/devoops/eventservice/repository/DirectorRepository.java
  • services/spring-event/src/main/java/tum/devoops/eventservice/repository/TrainerRepository.java
  • services/spring-event/src/main/java/tum/devoops/eventservice/service/EventService.java
  • services/spring-event/src/test/java/tum/devoops/eventservice/service/EventServiceTest.java
✅ Files skipped from review due to trivial changes (2)
  • services/spring-event/src/main/java/tum/devoops/eventservice/repository/TrainerRepository.java
  • services/spring-event/src/main/java/tum/devoops/eventservice/repository/DirectorRepository.java

Comment on lines +192 to +215
// Per the API contract: trainers may only create events for a team they coach; directors
// only for a sport they direct (either linked directly via sports_linked, or via one of the
// linked teams' sport). Admins bypass this entirely (checked by the caller).
private boolean canCreateEvent(UUID requesterId, EventCreate body) {
List<UUID> teamIds = body.getTeamsLinked() == null
? List.of()
: body.getTeamsLinked().stream().map(t -> parseUuid(t, "teams_linked")).collect(Collectors.toList());

boolean isTrainerOfTeam = teamIds.stream()
.anyMatch(teamId -> trainerRepository.existsById(new TrainerEntity.Id(teamId, requesterId)));
if (isTrainerOfTeam) {
return true;
}

Set<UUID> sportIds = new HashSet<>();
if (body.getSportsLinked() != null) {
sportIds.addAll(body.getSportsLinked());
}
teamRepository.findAllById(teamIds).forEach(team -> sportIds.add(team.getSportId()));

return sportIds.stream()
.anyMatch(sportId -> directorRepository.existsById(new DirectorEntity.Id(sportId, requesterId)));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the service and nearby tests
git ls-files 'services/spring-event/**' | sed -n '1,200p'

echo '--- EventService outline ---'
ast-grep outline services/spring-event/src/main/java/tum/devoops/eventservice/service/EventService.java --view expanded

echo '--- Search for canCreateEvent and create-event tests ---'
rg -n "canCreateEvent|teamsLinked|sportsLinked|create event|EventCreate" services/spring-event/src/test services/spring-event/src/main/java -S

Repository: AET-DevOps26/team-devoops

Length of output: 10170


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- EventService.java (100-220) ---'
sed -n '100,220p' services/spring-event/src/main/java/tum/devoops/eventservice/service/EventService.java | cat -n

echo '--- EventServiceTest.java (190-290) ---'
sed -n '190,290p' services/spring-event/src/test/java/tum/devoops/eventservice/service/EventServiceTest.java | cat -n

echo '--- EventCreate model fields ---'
sed -n '1,220p' services/spring-event/src/generated/java/tum/devoops/eventservice/model/EventCreate.java | cat -n

Repository: AET-DevOps26/team-devoops

Length of output: 18727


Check every linked team/sport before allowing create
services/spring-event/src/main/java/tum/devoops/eventservice/service/EventService.java:195-215 accepts the request if the requester matches any one linked team or any sport in the combined set. That lets a trainer/director mix one authorized link with unauthorized teams/sports and still create the event with the full payload. Add coverage for mixed authorized/unauthorized links and require each linked team/sport to be individually in scope.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@services/spring-event/src/main/java/tum/devoops/eventservice/service/EventService.java`
around lines 192 - 215, The canCreateEvent method in EventService currently
grants access when requesterId matches any single team or sport in the request,
which allows mixed authorized and unauthorized links to slip through. Update the
validation logic so every linked team in teamsLinked and every linked sport in
sportsLinked is checked individually against trainerRepository and
directorRepository, rather than using anyMatch over the whole set. Keep the
existing parsing and repository lookups, but change the authorization flow to
reject the request unless all linked entities are in scope for the requester.

@raphael-frank raphael-frank deleted the client/93-coach-role-pages branch July 6, 2026 13:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Coach (Trainer) role — pages

2 participants